#include "sound.h"


Sound::Sound(void)
{
  this->fileHandle = NULL;
}



Sound::Sound(LPWSTR fileName, Audio *audio) {
  this->fileName = Path::getFullSoundName(fileName);
  this->audio = audio;
  this->buffer8 = NULL;
  this->threadHandle = NULL;
  this->fileHandle = NULL;
  //Init variable
  loop = false;
  playState = SOUND_STOP;
}


Sound::~Sound(void)
{
  if(this->fileHandle != NULL)
  {
    this->Stop();
    CloseHandle(this->fileHandle);
  }
}


void Sound::WriteData(LPBYTE lpData, // pointer to input buffer
    DWORD dwOffSet, // offset of directsound buffer begin write
    DWORD dwSoundByte) { // size want to write 
  LPVOID lpvPtr1; // pointer to part1
	DWORD dwBytes1; // part1 length
	LPVOID lpvPtr2; // pointer to part2
	DWORD dwBytes2; // part2 length
	
	//lock memory
	hResult = buffer8->Lock(dwOffSet, dwSoundByte, &lpvPtr1, &dwBytes1, &lpvPtr2, &dwBytes2, 0);

	// if error occur
	if(DSERR_BUFFERLOST == hResult) // data will lost in this case
	{
		buffer8->Restore();
		hResult = buffer8->Lock(dwOffSet, dwSoundByte, &lpvPtr1, &dwBytes1, &lpvPtr2, &dwBytes2, 0);
	}
	if(hResult == DS_OK) // lock successful
	{
		memcpy( lpvPtr1, lpData, dwBytes1);
		if(dwBytes2 >0) // write the second part
			memcpy(lpvPtr2, lpData + dwBytes1, dwBytes2);
	} else {
    Log::Write(L"Lock directsound buffer fail");
  }
	hResult = buffer8->Unlock(lpvPtr1,dwBytes1, lpvPtr2, dwBytes2);
	if(hResult != DS_OK)
	{
    Log::Write(L"Unlock directsound buffer fail");
	} 
}


 void Sound::WriteBuffer(mad_pcm *pcm) {
  // pcm buffer, join data from pcm structure
  short pcmBuffer[PCM_BUFFER_SIZE];
  if (buffer8 == NULL) {// not create buffer yet
    buffer8 = audio->CreateSoundBuffer(pcm, this->bufferSize);
  }
  // prepare pcm buffer 16 bit
  short part1 = 0;
  short part2 = 0;
  int count = 0;
  BYTE *pBuffer;
  pBuffer = (BYTE*)&pcmBuffer;
  while (count < 1152) {
    part1 = (short)pcm->samples[0][count];
    part2 = (short)pcm->samples[1][count];
    memcpy(pBuffer, &part1, 2); // copy left channel
    pBuffer += 2;
    memcpy(pBuffer, &part2, 2); // copy right channel
    pBuffer += 2;
    count++;
  };
  pBuffer = (BYTE*)&pcmBuffer;
  count = PCM_BUFFER_SIZE * 2;
  //write to directsound buffer
  int writeSize = 0;
  int sub = 0;
  count = PCM_BUFFER_SIZE * 2; // reset count value
  do {
    // recalculate new position and  write size
    buffer8->GetCurrentPosition(&playPos, NULL);
    sub = writePos - playPos;
    if (sub == 0) {
      if (firstFillBuffer == true) {
        firstFillBuffer = false;
        writeSize = this->bufferSize;
      } else {
        prevPlayPos = playPos;
        Sleep(50); 
        buffer8->GetCurrentPosition(&playPos, NULL);
        sub = writePos - playPos;
        if (prevPlayPos == playPos) { // don't change play position
          Sleep(50); // buffer full, do nothing
          continue;
        } else {
          if(sub > 0) {
            writeSize = this->bufferSize - sub;
          } else {
            writeSize = abs(sub);
          }
        }
      }
    } else {
      if(sub > 0) {
        writeSize = this->bufferSize - sub;
      } else {
        writeSize = abs(sub);
      }
    }

    /*File buffer*/

    if (writeSize == count) { // enough buffer
      this->WriteData(pBuffer, writePos, writeSize);
      writePos = writePos + writeSize;
			if(writePos >= (DWORD)bufferSize)
			{
				writePos = writePos - bufferSize;
			}
      break;
    }
    if ( writeSize > count) { // directsound buffer remain is larger
      this->WriteData(pBuffer, writePos, count);
      writePos = writePos + count;
			if(writePos >= (DWORD)bufferSize)
			{
				writePos = writePos - bufferSize;
			}
      break;
    }
    if (writeSize < count) { // directsound buffer is not enough
      this->WriteData(pBuffer, writePos, writeSize);
      count = count - writeSize;
      pBuffer = pBuffer + writeSize;
      writePos = writePos + writeSize;
			if(writePos >= (DWORD)bufferSize)
			{
				writePos = writePos - bufferSize;
			}
      Sleep(50);
    }
  } while (count > 0);
  Sleep(20); // bitrate enough
  //_CrtDumpMemoryLeaks();
}


mad_flow Sound::input(void *data,
      mad_stream *stream) {
  Buffer *bufferData = (Sound::Buffer*)data;
  if (bufferData->length == 0) {
    return MAD_FLOW_STOP;
  }
  // streaming input buffer to decode
  mad_stream_buffer(stream, bufferData->start, bufferData->length);
  bufferData->length = 0;
  return MAD_FLOW_CONTINUE;
}


mad_flow Sound::error(void *data,
      mad_stream *stream,
      mad_frame *frame) {
  Buffer *bufferData = (Sound::Buffer*)data;
  fprintf(stderr, "decoding error 0x%04x (%s) at byte offset %u\n",
	  stream->error, ::mad_stream_errorstr(stream),
	  stream->this_frame - bufferData->start);
  return MAD_FLOW_CONTINUE;
}
  

signed int Sound::scale(mad_fixed_t &sample){
  /* round */
  sample += (1L << (MAD_F_FRACBITS - 16));

  /* clip */
  if (sample >= MAD_F_ONE)
    sample = MAD_F_ONE - 1;
  else if (sample < -MAD_F_ONE)
    sample = -MAD_F_ONE;

  /* quantize */
  sample = sample >> (MAD_F_FRACBITS + 1 - 16);
  return sample;
}


mad_flow Sound::output(void *data,
      const mad_header *header, mad_pcm *pcm) {
  Buffer *bufferData = (Sound::Buffer*)data;
  return bufferData->instance->InstanceOutput(data, header, pcm);
}


mad_flow Sound::InstanceOutput(
      void *data,
      const mad_header *header, 
      mad_pcm *pcm) {
  /* scale to 16 bit */
  int nchannel = pcm->channels;
  int length = pcm->length;
  int *leftChannel = pcm->samples[0];
  int *rightChannel = pcm->samples[1];
  while (length > 0) {
    scale(*leftChannel);
    leftChannel++;
    if (nchannel == 2) {
      scale(*rightChannel);
      rightChannel++;
    }
    length--;
  }
  // call function write to buffer
  this->WriteBuffer(pcm); 
  if (this->playState == SOUND_STOP) {
    return MAD_FLOW_STOP; // stop decode
  } 
  return MAD_FLOW_CONTINUE;
}


int Sound::Decode() {
  do {
  Buffer bufferData;
  mad_decoder decoder;
  bufferData.start = (unsigned char*)mapPointer;
  bufferData.length = fileLength;
  bufferData.instance = this;

  // init decode
  mad_decoder_init(&decoder, &bufferData, 
      Sound::input, 0, 0,
      Sound::output, Sound::error, 0);
  mad_decoder_run(&decoder, MAD_DECODER_MODE_SYNC);
  mad_decoder_finish(&decoder);
  } while (this->loop == true && this->playState != SOUND_STOP);
  return 0;
}


int WINAPI Sound::DecodeCallback(void* param) {
  Sound* instance = (Sound*)param;
  int exitcode = instance->Decode();
  return exitcode;
}


void Sound::Prepare() {
  //map file
    fileHandle = CreateFileW(
      this->fileName,
      GENERIC_READ, 
      FILE_SHARE_READ,
      NULL,
      OPEN_EXISTING,
      FILE_ATTRIBUTE_NORMAL,
      NULL);
  LARGE_INTEGER fileSize;
  GetFileSizeEx(fileHandle, &fileSize);
  fileLength = fileSize.LowPart;
  fileHandle = CreateFileMapping(
      fileHandle, 
      NULL,
      PAGE_READONLY, 
      0, 
      0, 
      NULL);
  mapPointer = MapViewOfFile(fileHandle, FILE_MAP_READ, 0, 0, 0);
  //Log::ErrorExit(L"Sound::Prepare");
  // Init variable
  playPos = 0;
  writePos = 0;
  prevPlayPos = 0;
  firstFillBuffer = true;
  // start thread fill data to sound buffer
  threadHandle = CreateThread(
      NULL, 0, 
      (LPTHREAD_START_ROUTINE)Sound::DecodeCallback, this,
      THREAD_QUERY_INFORMATION, NULL);
}


void Sound::Play(bool loop) {
  if (this->playState == SOUND_PLAY) {
    return;
  }
  this->playState = SOUND_PLAY;
  if (buffer8 == NULL) { // need prepare
    this->loop = loop;
    this->Prepare();
  }
  while (buffer8 == NULL) {
    Sleep(10);
  }
  hResult = buffer8->SetVolume(DSBVOLUME_MAX);
  hResult = buffer8->Play(NULL, NULL, DSBPLAY_LOOPING);
  if (hResult != DS_OK) 
    Log::Write(L"play fail", this->fileName);
}


void Sound::Pause() {
  this->playState = SOUND_PAUSE;
  hResult = buffer8->Stop();
  if (hResult != DS_OK) {
    Log::Write(L"Pause play directsoundbuffer8 fail", this->fileName);
  }
}


void Sound::Stop() {
  this->playState = SOUND_STOP;
  if (buffer8 == NULL) {
    return;
  }
  hResult = buffer8->Stop();
  if (hResult != DS_OK) {
    Log::Write(L"Stop play directsoundbuffer8 fail", this->fileName);
  }
  // wait stop thread
  LPDWORD exitCode = new DWORD;
  do {
     GetExitCodeThread(threadHandle, exitCode);
     Sleep(1);
  } while (*exitCode != 0);
  delete exitCode;
  /* Unview map file */
  if (mapPointer != NULL) {
    UnmapViewOfFile(mapPointer);
  }
  buffer8->Release();
  buffer8 = NULL;
}
